﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GraphosData;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Media.Animation;
using System.ComponentModel;
using System.Windows.Controls;

namespace GraphosControls
{

	public class TransitionAnimationCompleteEventArgs : EventArgs
	{
		public TransitionAnimationCompleteEventArgs(IEnumerable<UIElement> elems)
		{
			ElemetsDoneAnimating = elems;
		}
		public IEnumerable<UIElement> ElemetsDoneAnimating { get; private set; }
	}

	public class GraphViewerAnimation : INotifyPropertyChanged
	{
		private static class Const
		{
			public static TimeSpan CreateNodeTime = TimeSpan.FromSeconds(1.5);
			public static TimeSpan CreateEdgeTime = TimeSpan.FromSeconds(1.0);
			public static TimeSpan CreateLabelTime = TimeSpan.FromSeconds(1.5);
			public static TimeSpan DestroyNodeTime = TimeSpan.FromSeconds(0.5);
			public static TimeSpan DestroyEdgeTime = TimeSpan.FromSeconds(0.75);
			public static TimeSpan DestroyLabelTime = TimeSpan.FromSeconds(0.5);
		}
		private class DummyNode
		{
			public double Charge { get; set; }
			public Point Position { get; set; }

		}

		public GraphViewerAnimation()
		{
			m_animCompleteTimer = new DispatcherTimer();
			m_animCompleteTimer.Tick += TransitionAnimationsComplete_Tick;
			m_animCompleteTimer.Interval = TimeSpan.FromSeconds(1.5);
			m_timeStep = 0.03;
			m_mouseCharge = new DummyNode();
			m_centerSpring = new DummyNode();

			m_spring = 20;
			m_damping = 0.75;
			m_edgeLength = 50;
			m_charge = 400;
		}

		//public bool MouseRepulse
		//{
		//    get { return m_mouseRepulse; }
		//    set { m_mouseRepulse = value; }
		//}
		//public bool MouseAttract
		//{
		//    get { return m_mouseAttract; }
		//    set { m_mouseAttract = value; }
		//}
		//public bool IsInitialized
		//{
		//    get { return m_animationInitialized; }
		//}

		private VisualNode m_dragNode;
		private DummyNode m_dragDummy;
		private Point m_dragPosition;
		private bool m_dragging = false;



		public VisualNode DragNode
		{
			get { return m_dragNode; }
			set
			{
				if (m_dragNode != value)
				{
					if (value == null)
					{
						ReleaseDragNode();
						return;
					}

					if (m_dragNode != null)
						ReleaseDragNode();

					SetDragNode(value);
				}
			}
		}
		public Point DragPosition
		{
			get { return m_dragPosition; }
			set
			{
				m_dragPosition = value;
				m_dragDummy.Position = m_dragPosition;
				Start(m_rootNode, m_width, m_height);
			}
		}

		private void SetDragNode(VisualNode node)
		{
			m_dragNode = node;
			m_dragDummy = new DummyNode();
			m_dragging = m_dragNode.CaptureMouse();


			if (!m_dragging)
				ReleaseDragNode();
		}
		private void ReleaseDragNode()
		{
			m_dragNode.ReleaseMouseCapture();
			m_dragNode = null;
			m_dragPosition = new Point();
			m_dragging = false;
			m_dragDummy = null;
		}


		List<VisualEdge> m_edges = new List<VisualEdge>();

		public void SetEdges(List<VisualEdge> edges)
		{
			m_edges = edges;
		}



		#region Transition Animation Logic

		public delegate void AnimationCompleteEventHandler(object sender, TransitionAnimationCompleteEventArgs e);
		public event AnimationCompleteEventHandler TransitionAnimationComplete;
		private void OnTransitionAnimationComplete(IList<UIElement> elems)
		{
			if (TransitionAnimationComplete != null && elems.Count > 0)
			{
				TransitionAnimationComplete(this, new TransitionAnimationCompleteEventArgs(elems));
			}
			m_elementsBeingAnimated = new List<UIElement>();
		}

		private void TransitionAnimationsComplete_Tick(object sender, EventArgs e)
		{
			DispatcherTimer timer = sender as DispatcherTimer;
			timer.Stop();

			OnTransitionAnimationComplete(m_elementsBeingAnimated);
		}

		public void ApplyCreateNodeAnimation(VisualNode node)
		{
			DoubleAnimation opacity = new DoubleAnimation();
			opacity.To = 1.0;
			opacity.Duration = Const.CreateNodeTime;
			opacity.AccelerationRatio = 0.75;
			opacity.DecelerationRatio = 0.25;
			opacity.BeginTime = TimeSpan.FromSeconds(0.0);
			opacity.Freeze();
			node.BeginAnimation(VisualNode.OpacityProperty, opacity);

		}
		public void ApplyCreateEdgeAnimation(UIElement elem)
		{
			DoubleAnimation opacity = new DoubleAnimation();
			opacity.To = 1.0;
			opacity.Duration = Const.CreateEdgeTime;
			opacity.AccelerationRatio = 0.75;
			opacity.DecelerationRatio = 0.25;
			opacity.BeginTime = TimeSpan.FromSeconds(0.0);
			opacity.Freeze();
			elem.BeginAnimation(UIElement.OpacityProperty, opacity);
		}
		public void ApplyCreateLabelAnimation(UIElement elem)
		{
			DoubleAnimation opacity = new DoubleAnimation();
			opacity.To = 1.0;
			opacity.Duration = Const.CreateLabelTime;
			opacity.AccelerationRatio = 0.75;
			opacity.DecelerationRatio = 0.25;
			opacity.BeginTime = TimeSpan.FromSeconds(0.0);
			opacity.Freeze();
			elem.BeginAnimation(UIElement.OpacityProperty, opacity);
		}
		public void ApplyDestroyNodeAnimation(VisualNode node)
		{
			m_elementsBeingAnimated.Add(node);
			m_animCompleteTimer.Start();

			DoubleAnimation opacity = new DoubleAnimation();
			opacity.To = 0.0;
			opacity.AccelerationRatio = 0.25;
			opacity.DecelerationRatio = 0.75;
			opacity.Duration = Const.DestroyNodeTime;
			opacity.Freeze();
			node.BeginAnimation(VisualNode.OpacityProperty, opacity);
		}
		public void ApplyDestroyEdgeAnimation(UIElement elem)
		{
			m_elementsBeingAnimated.Add(elem);
			m_animCompleteTimer.Start();

			DoubleAnimation opacity = new DoubleAnimation();
			opacity.To = 0.0;
			opacity.AccelerationRatio = 0.25;
			opacity.DecelerationRatio = 0.75;
			opacity.Duration = Const.DestroyEdgeTime;
			opacity.Freeze();
			elem.BeginAnimation(UIElement.OpacityProperty, opacity);
		}
		public void ApplyDestroyLabelAnimation(UIElement elem)
		{
			m_elementsBeingAnimated.Add(elem);
			m_animCompleteTimer.Start();

			DoubleAnimation opacity = new DoubleAnimation();
			opacity.To = 0.0;
			opacity.AccelerationRatio = 0.25;
			opacity.DecelerationRatio = 0.75;
			opacity.Duration = Const.DestroyLabelTime;
			opacity.Freeze();
			elem.BeginAnimation(UIElement.OpacityProperty, opacity);
		}
		private DispatcherTimer m_animCompleteTimer;
		private List<UIElement> m_elementsBeingAnimated = new List<UIElement>();

		#endregion

		#region Position Animation Logic

		public event RoutedEventHandler PositionAnimationComplete;
		private void OnPositionAnimationComplete()
		{
			if (PositionAnimationComplete != null)
				PositionAnimationComplete(this, new RoutedEventArgs());
		}

		public void Start(VisualNode rootNode, double width, double height)
		{

			if (!m_isAnimating)
			{
				// add the handler for the animation
				CompositionTarget.Rendering += PositionAnimation_Rendering;
				m_isAnimating = true;
			}
			if (m_rootNode != rootNode)
			{
				m_rootNode = rootNode;

				m_numberEdges = rootNode.AdjacentVisualNodes.Count + 1;
				foreach (VisualNode node in rootNode.AdjacentVisualNodes)
					m_numberEdges += node.AdjacentVisualNodes.Count;

				// set the cut off value
				m_kineticEnergyLimit = Math.Pow(m_numberEdges, 1.5);
			}

			foreach (VisualEdge edge in m_edges)
			{
				edge.CenterNode.Charge = m_charge;
				edge.CenterNode.Mass = 1.0;
			}

			// set the new width and height
			m_width = width;
			m_height = height;

			// set the mouse charge and center spring values
			m_mouseCharge.Charge = m_charge;
			m_centerSpring.Position = new Point(m_width / 2, m_height / 2);

			// set all the node charges
			foreach (var node in m_rootNode.AdjacentVisualNodes)
			{
				node.Mass = 1.0;
				node.Charge = m_charge;
			}

			m_rootEdgeLength = Math.Sqrt(Math.Pow(width, 2) + Math.Pow(height, 2)) / 6;
		}
		public void Pause()
		{
			CompositionTarget.Rendering -= PositionAnimation_Rendering;
			OnPositionAnimationComplete();
			m_isAnimating = false;
		}
		public void Stop()
		{
			foreach (var node in m_rootNode.AdjacentVisualNodes)
			{
				node.ResetKeneticValues();
			}
			foreach (VisualEdge edge in m_edges)
			{
				edge.CenterNode.ResetKeneticValues();
			}
			CompositionTarget.Rendering -= PositionAnimation_Rendering;
			OnPositionAnimationComplete();
			m_isAnimating = false;
		}

		private void PositionAnimation_Rendering(object sender, EventArgs e)
		{
			double totalKineticEnergy = 0;
			DummyNode dragNodeDummy = new DummyNode();
			dragNodeDummy.Position = DragPosition;

			foreach (VisualEdge edge in m_edges)
			{
				edge.CenterNode.NetForce = new Vector(0, 0);

				foreach (VisualEdge otherEdge in m_edges.Where(ed => ed != edge))
				{
					edge.CenterNode.NetForce += CalculateRepulsion(edge.CenterNode.Position, otherEdge.CenterNode.Position,
						 10, 10);
				}

				//foreach (VisualNode node in AllVisualNodes(m_rootNode))
				//{
				//   edge.CenterNode.NetForce += CalculateRepulsion(edge.CenterNode.Position, node.Position, 50, 50);
				//}

				edge.CenterNode.NetForce += CalculateAttraction(edge.CenterNode.Position, edge.VisualNodeFrom.Position, edge.Length / 2.0, 40);
				edge.CenterNode.NetForce += CalculateAttraction(edge.CenterNode.Position, edge.VisualNodeTo.Position, edge.Length / 2.0, 40);

				edge.CenterNode.Velocity = (edge.CenterNode.Velocity + m_timeStep * edge.CenterNode.NetForce) * m_damping;
				edge.CenterNode.Position += m_timeStep * edge.CenterNode.Velocity;
			}



			foreach (VisualNode node in AllVisualNodes(m_rootNode))
			{
				node.NetForce = new Vector(0, 0);

				//if (m_mouseRepulse)
				//    node.NetForce += CalculateRepulsion(node, m_mouseCharge);

				//if (m_mouseAttract)
				//    node.NetForce += CalculateAttraction(node, m_mouseCharge, m_edgeLength / 2, m_spring / 2);
				if (m_dragging && node == m_dragNode)
				{
					m_dragNode.NetForce = CalculateAttraction(node.Position, m_dragDummy.Position, 0, 100);
				}
				else if (node == m_rootNode)
				{
					node.NetForce = CalculateAttraction(node.Position, m_centerSpring.Position, 0, m_spring);
				}
				else
				{

					List<DummyNode> sides = new List<DummyNode>() { new DummyNode(), new DummyNode(), new DummyNode(), new DummyNode() };

					sides[0].Position = new Point(node.Position.X, (node.Position.Y - Math.Abs(node.Position.Y)));
					sides[1].Position = new Point(node.Position.X, (node.Position.Y + Math.Abs(m_height - node.Position.Y)));
					sides[2].Position = new Point((node.Position.X - Math.Abs(node.Position.X)), node.Position.Y);
					sides[3].Position = new Point((node.Position.X + Math.Abs(m_width - node.Position.X)), node.Position.Y);

					foreach (VisualNode otherNode in AllVisualNodes(m_rootNode).Where(n => n != node))
					{
						node.NetForce += CalculateRepulsion(node.Position, otherNode.Position, node.Charge, otherNode.Charge);
					}
					foreach (VisualNode connectedNode in node.AdjacentVisualNodes)
					{
						if (connectedNode == m_rootNode)
						{
							node.NetForce += CalculateAttraction(node.Position, connectedNode.Position, m_rootEdgeLength, m_spring);
						}
						else
						{
							node.NetForce += CalculateAttraction(node.Position, connectedNode.Position, m_edgeLength, m_spring);
						}
					}
					foreach (var side in sides)
					{
						if (node.AdjacentVisualNodes.Count == 1)
							side.Charge = 3 * node.Charge;
						else
							side.Charge = node.Charge;

						node.NetForce += CalculateRepulsion(node.Position, side.Position, node.Charge, side.Charge);
					}

					//foreach (VisualEdge edge in m_edges)
					//{
					//    node.NetForce += CalculateRepulsion(node.Position, edge.CenterNode.Position, 100, 100);
					//}
				}

				node.Velocity = (node.Velocity + m_timeStep * node.NetForce) * m_damping;
				node.Position += m_timeStep * node.Velocity;
				totalKineticEnergy += node.Mass * node.Velocity.LengthSquared;


			}
			if (totalKineticEnergy < m_kineticEnergyLimit)
			{
				Stop();
			}

		}

		public double TimeStep
		{
			get { return m_timeStep; }
			set { m_timeStep = value; }
		}
		public double Charge
		{
			get { return m_charge; }
			set
			{
				m_charge = value;
				OnPropertyChanged("Charge");
				Start(m_rootNode, m_width, m_height);
			}
		}
		public double EdgeLength
		{
			get { return m_edgeLength; }
			set
			{
				m_edgeLength = value;
				OnPropertyChanged("EdgeLength");
				Start(m_rootNode, m_width, m_height);
			}
		}
		public double Spring
		{
			get { return m_spring; }
			set
			{
				m_spring = value;
				OnPropertyChanged("Spring");
				Start(m_rootNode, m_width, m_height);
			}
		}
		public double Damping
		{
			get { return m_damping; }
			set
			{
				m_damping = value;
				OnPropertyChanged("Damping");
				Start(m_rootNode, m_width, m_height);
			}
		}
		public bool IsAnimating
		{
			get { return m_isAnimating; }
			set { m_isAnimating = value; }
		}

		private double m_timeStep;
		private double m_charge;
		private double m_spring;
		private double m_edgeLength;
		private double m_rootEdgeLength;
		private double m_damping;
		private bool m_isAnimating;

		private double m_width;
		private double m_height;
		private int m_numberEdges;
		private double m_kineticEnergyLimit;
		private DummyNode m_mouseCharge;
		private DummyNode m_centerSpring;
		private VisualNode m_rootNode;

		// Get all the visual nodes (including the root)
		private static IEnumerable<VisualNode> AllVisualNodes(VisualNode root)
		{
			yield return root;
			foreach (VisualNode node in root.AdjacentVisualNodes)
				yield return node;
		}

		// Calculate the attraction of two visual nodes (or dummy nodes)
		private static Vector CalculateAttraction(Point sourcePosition, Point targetPosition,
			 double edgeLength, double spring)
		{
			Vector force = (sourcePosition - targetPosition);
			double displacement = force.Length - edgeLength;

			force.Normalize();
			force.Negate();

			return spring * force * displacement;
		}


		// Calculate the repulsion of two visual nodes (or dummy nodes)
		private static Vector CalculateRepulsion(Point sourcePosition, Point targetPosition,
			 double sourceCharge, double targetCharge)
		{
			Vector force = (sourcePosition - targetPosition);
			return (sourceCharge * targetCharge * force) / force.LengthSquared;
		}


		//public void MoveMouseCharge(Point mouseLocation)
		//{
		//    m_mouseCharge.Position = mouseLocation;
		//}


		#endregion

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;
		private void OnPropertyChanged(string info)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(info));
		}

		#endregion
	}
}
